package hydrap2p.download;

import hydrap2p.helper.RangeUtils;
import hydrap2p.library.Range;

import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * The Class Download.
 */
public class Download extends BaseDownload implements Serializable {
	/**
	 * Serialization constant
	 */
	private static final long serialVersionUID = 1L;
	
	//Data
	private File downloadDir;
	private Map<File, Long> files;
	private List<Long> sizes;
	
	/**
	 * Instantiates a new download.
	 * 
	 * @param sources the sources
	 * @param downloadDir the download dir
	 * @param files the files
	 * @param sizes the sizes
	 * @param priority the priority
	 * @param state the state
	 */
	public Download(List<Source> sources, String downloadDir, List<String> files, List<Long> sizes, DownloadPriority priority, DownloadState state) {
		//Validation
		if(!Arrays.asList(DownloadPriority.values()).contains(priority) || !Arrays.asList(DownloadState.values()).contains(state))
			throw new IllegalArgumentException();
		
		if(sources == null)
			sources = new ArrayList<Source>();
		
		File dlDir;
		if(downloadDir != null)
			dlDir = new File(downloadDir);
		else
			throw new IllegalArgumentException();
		
		//Files and sizes should map one-to-one and exist
		if(files != null && sizes != null) {
			if(files.size() != sizes.size()) {
				throw new IllegalArgumentException();
			}
			if(files.contains(null) || sizes.contains(null)) {
				throw new IllegalArgumentException();
			}
		
			//Size must be positive
			for(Long size : sizes)
				if(size < 0)
					throw new IllegalArgumentException();
		}
		
		//Assignment
		this.sources = sources;
		this.state = state;
		this.uid = -1;
		this.priority = priority;
		this.downloadDir = dlDir;
		this.files = Collections.synchronizedMap(new TreeMap<File, Long>());
		this.sizes = Collections.synchronizedList(sizes);
		
		for(int x = 0;x < files.size();x++) {
			File temp = new File(dlDir.getPath() + File.pathSeparator + files.get(x));
			this.files.put(temp, sizes.get(x));
		}
		
		this.available = Collections.synchronizedList(new ArrayList<Range>());
		this.verified = Collections.synchronizedList(new ArrayList<Range>());
		this.downloaded = Collections.synchronizedList(new ArrayList<Range>());
	}
	
	/**
	 * Recalculate available ranges.
	 */
	public void recalculateAvailableRanges() {
		List<Range> newAvailable = new ArrayList<Range>();
		
		for(Source s : sources) {
			if(s.getAvailable() != null) {
				for(Range r : s.getAvailable())
					newAvailable = RangeUtils.merge(newAvailable, r);
			}
		}
		
		available = newAvailable;
	}
	
	/**
	 * Adds the verified range.
	 * 
	 * @param r the r
	 */
	public void addVerifiedRange(Range r) {
		verified = RangeUtils.merge(verified, r);
	}
	
	/**
	 * Adds the downloaded range.
	 * 
	 * @param r the r
	 */
	public void addDownloadedRange(Range r) {
		downloaded = RangeUtils.merge(downloaded, r);
	}
	
	/**
	 * Adds the available range.
	 * 
	 * @param r the r
	 */
	public void addAvailableRange(Range r) {
		available = RangeUtils.merge(available, r);
	}
	
	/**
	 * Gets the total downloaded.
	 * 
	 * @return the total downloaded
	 */
	public long getTotalDownloaded() {
		long total = 0;
		
		for(Range r : downloaded)
			total += r.len;
		
		return total;
	}
	
	/**
	 * Gets the total size.
	 * 
	 * @return the total size
	 */
	public long getTotalSize() {
		long total = 0;
		
		for(Long n : sizes)
			total += n;
		
		return total;
	}
	
	/* (non-Javadoc)
	 * @see hydrap2p.download.BaseDownload#getState()
	 */
	public DownloadState getState() {
		return state;
	}

	/* (non-Javadoc)
	 * @see hydrap2p.download.BaseDownload#setState(hydrap2p.download.State)
	 */
	public BaseDownload setState(DownloadState state) {
		if(Arrays.asList(DownloadState.values()).contains(state))
			this.state = state;
		
		return this;
	}

	/* (non-Javadoc)
	 * @see hydrap2p.download.BaseDownload#getUid()
	 */
	public long getUid() {
		return uid;
	}

	/* (non-Javadoc)
	 * @see hydrap2p.download.BaseDownload#setUid(long)
	 */
	public BaseDownload setUid(long uid) {
		this.uid = uid;
		
		return this;
	}

	/* (non-Javadoc)
	 * @see hydrap2p.download.BaseDownload#getPriority()
	 */
	public DownloadPriority getPriority() {
		return priority;
	}

	/* (non-Javadoc)
	 * @see hydrap2p.download.BaseDownload#setPriority(hydrap2p.download.Priority)
	 */
	public BaseDownload setPriority(DownloadPriority priority) {
		if(Arrays.asList(DownloadPriority.values()).contains(priority))
			this.priority = priority;
		
		return this;
	}

	/**
	 * Gets the download dir.
	 * 
	 * @return the download dir
	 */
	public File getDownloadDir() {
		return downloadDir;
	}

	/**
	 * Sets the download dir.
	 * 
	 * @param downloadDir the new download dir
	 */
	public void setDownloadDir(File downloadDir) {
		this.downloadDir = downloadDir;
	}

	/**
	 * Gets the files.
	 * 
	 * @return the files
	 */
	public Map<File, Long> getFiles() {
		return files;
	}

	/**
	 * Sets the files.
	 * 
	 * @param files the files
	 */
	public void setFiles(Map<File, Long> files) {
		this.files = files;
	}

	/* (non-Javadoc)
	 * @see hydrap2p.download.BaseDownload#setSources(java.util.List)
	 */
	public BaseDownload setSources(List<Source> sources) {
		this.sources = sources;
		
		recalculateAvailableRanges();
		
		return this;
	}

	/* (non-Javadoc)
	 * @see hydrap2p.download.BaseDownload#getSources()
	 */
	public List<Source> getSources() {
		return sources;
	}

	/**
	 * Adds the source.
	 * 
	 * @param arg1 the arg1
	 * @return true, if successful
	 */
	public boolean addSource(Source arg1) {
		boolean result = sources.add(arg1);
		
		if(result)
			recalculateAvailableRanges();
		
		return result;
	}
	
	/**
	 * Removes the source.
	 * 
	 * @param arg1 the arg1
	 * @return true, if successful
	 */
	public boolean removeSource(Source arg1) {
		boolean result = sources.remove(arg1);
		
		if(result)
			recalculateAvailableRanges();
		
		return result;
	}
	
	/**
	 * Checks if is torrent.
	 * 
	 * @return true, if is torrent
	 */
	public boolean isTorrent() {
		for(Source s : sources) {
			if(s.getProtocol().equals("bittorrent")) {
				return true;
			}
		}
		
		return false;
	}
}
